跳到主要内容

MyBatisPlus 常用的插件工具

插件机制

MyBatis 允许你在 已映射语句 执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

1383365-20190702172248436-339645944.png

总体概况为: 1、拦截执行器的方法 2、拦截参数的处理 3、拦截结果集的处理 4、拦截 SQL 语法构建的处理

如何自定义一个插件

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

@Intercepts({@Signature(
// 指定要拦截的类型
type= Executor.class,
method = "update", // 要拦截的方法
args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 只有执行的 SQL 是上面 method 指定的类型才会执行这个方法
// 拦截方法:具体业务逻辑编写的位置
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
// 创建 target 对象的代理对象,目的是将当前拦截器加入到该对象中
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
// 属性设置
}
}

然后再注入这个拦截器

@Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}

攻击 SQL 阻断解析器

在 MP 中提供了对 SQL 执行的分析的插件,可用于作阻断全表更新、删除的操作(这个插件只适用于开发环境,不适用生产环境)

官网的配置已经过期了,现在配置插件都像如下这种方式

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加拦截器
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}

注意,这个需要添加依赖,否则会报错

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>

写个删除全部数据的测试方法,就能自动报错阻断了

@Test
void deleteAllTest() {
int i = userMapper.delete(null);
log.info("删除了:{} 次", i);
}

性能分析插件

参考资料 执行 SQL 分析打印

性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超出时间会抛出异常(这个插件只适用于开发环境,不适用生产环境)

注意:官方于 mybatis-plus3.2 版本移除了性能分析插件,所以需要自己导入插件

<!-- https://mvnrepository.com/artifact/p6spy/p6spy -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/studyPlus?useUnicode=true&characterEncoding=utf8&useSSL=true&useServerPrepStmts=true
username: root
password: root
  • driver-class-name 为 p6spy 提供的驱动类
  • url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
  • 打印出 sql 为null,在 excludecategories 增加 commit
  • 批量操作不打印 sql,去除 excludecategories 中的 batch
  • 批量操作打印重复的问题请使用 MybatisPlusLogFactory (3.2.1新增)
  • 该插件有性能损耗,不建议生产环境使用。

编写一个 spy.properties 配置文件

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

执行测试方法

@Test
void selectTest() {
List<User> userList = userMapper.selectList(null);
userList.forEach(x -> log.info(x.toString()));
}

输出的测试结果

 Consume Time:13 ms 2020-12-01 20:16:40
Execute SQL:SELECT id,name,age,email FROM user

SQL 自动填充

参考资料 官方文档 自动填充功能

当插入数据时可能会希望能自动填充那些没有传值的字段,比如当前时间之类的东西,在 MP 中提供了自动填充机制,只需使用 @TableField 注解

@Data
public class User extends Model<User> {
// 设置 id 为自增长
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
// 设置插入数据时会自动填充
@TableField(fill = FieldFill.INSERT)
private String email;
}

自定义实现类 MyMetaObjectHandler

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

// FieldFill.INSERT 时自动填充
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
//注意方法名称与下面的 updateFill 不一样
this.strictInsertFill(metaObject, "email", () -> "temp@email.com", String.class); // 起始版本 3.3.3(推荐)
}

// FieldFill.UPDATE 时自动填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
// 同上
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}

注意事项:

  • 填充原理是直接给 entity 的属性设置值!!!
  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是 null
  • MetaObjectHandler 提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为 null 则不填充
  • 字段必须声明 TableField 注解,属性 fill 选择对应策略,该声明告知 Mybatis-Plus 需要预留注入 SQL 字段
  • 填充处理器 MyMetaObjectHandler 在 Spring Boot 中需要声明 @Component@Bean 注入
  • 要想根据注解 FieldFill.xxx 和字段名以及字段类型来区分必须使用父类的 strictInsertFill 或者 strictUpdateFill 方法
  • 不需要根据任何来区分可以使用父类的 fillStrategy 方法

编写测试插入数据

@Test
void testInsert() {
User user = new User();
user.setAge(18);
user.setName("赵六");
// 改变的行数
int insert = userMapper.insert(user);
// 会自动把这个自增的 id 属性回填到 User里面
log.info("回填的 id:{}", user.getId());
}

逻辑删除

参考资料 官方文档 逻辑删除

开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,但是实际并非物理删除,而是通过某个字段标识为删除,查询时携带状态条件,确保被标记的数据不被查询到。这样的目的就是避免真正的数据被删除

之后操作就变成如下这样

-- 删除: 
update user set deleted=1 where id = 1 and deleted=0
-- 查找:
select id,name,deleted from user where deleted=0

首先为表添加一个 deleted 字段,用于标识数据是否被 “删除”,1 代表删除,0 代表未删除

alter table tb_user
add deleted int default 0 not null comment '标识是否被删除'

同时也修改 User 实体,添加这个 deleted 属性,并添加 @TableLogic 注解

@TableLogic
private Integer deleted;

然后配置 application.yml

mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

编写测试代码:

@Test
void deleteByIdTest() {
int i = userMapper.deleteById(2L);
log.info("删除了:{} 次", i); // 依旧打印 "删除了:1 次"
}

查看控制台,发现最后执行的并不是真正的删除操作

UPDATE user SET deleted=1 WHERE id=2 AND deleted=0

同理,查询操作也会自动忽略

@Test
void selectAllUserTest() {
List<User> userList = userMapper.selectList(null);
userList.forEach(x -> log.info(x.toString()));
}
// Execute SQL:SELECT id,name,age,email,deleted FROM user WHERE deleted=0

通用枚举

参考资料 官方文档 通用枚举

就是对一些约定的参数进行映射,例如 flag 字段在数据库中是通过 int 类型存储的,0 代表 xx、1 代表 xxx,显然这样记很不方便,因此可以使用 “通用枚举” 将其转成枚举类型

例如这里添加一个字段,用 1、2 分别标识男女

alter table user
add sex int default 1 not null comment '1-男,2-女';

编写一个 SexEnum

public enum SexEnum implements IEnum<Integer> {
MAN(1, "男"),
WOMAN(2, "女");

private int value;
private String desc;

SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return this.value;
}

@Override
public String toString() {
return this.desc;
}
}

然后就能在 Entity 里使用这个字段了

private SexEnum sex;

在配置文件中加上枚举包的路径

mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.alsritter.*.enums

现在可以编写测试方法了

@Test
void selectManUserTest() {
// 甚至可以使用枚举做条件查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("sex", SexEnum.MAN);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(x -> log.info(x.toString()));
}

MybatisX 快速开发插件

代码生成器

参考资料 官方文档 代码生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以自动生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。(就是一个自动生成项目的工具,爽爆了!!!)

添加依赖

MyBatis-Plus 从 3.0.3 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:

直接创建新的空 Spring Boot 项目,然后把下面的依赖加上

<!-- 基本的 MyBatis Puls 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 代码生成器的东西 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,
如果选择了非默认引擎,需要在 AutoGenerator 中 设置模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>

生成器模板

然后粘贴下面的代码到项目中去

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import org.apache.commons.lang3.StringUtils;

import java.util.Scanner;

/**
* -mp代码生成工具类,使用freemarker引擎
* -使用前可以进行的修改
* gc.setAuthor 设置作者,会出现在类前
* gc.setOutputDir 设置输出路径,默认为 D:\
* pc.setParent 设置输出的包名(也即文件夹名)
* DataSourceConfig 设置数据源
* -实体类使用lombok,swagger2,驼峰命名,去除is_前缀
* -controller 使用rest风格
* -mapper.xml生成resultMap和baseColumnList
*
* @author alsritter
*/
public class CodeGenerator {

/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}

public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator();
// 设置模板引擎
mpg.setTemplateEngine(new VelocityTemplateEngine());
// 全局配置
GlobalConfig gc = new GlobalConfig();
//作者
gc.setAuthor("alsritter");
//路径,默认为 D:\
String projectPath = System.getProperty("user.dir");
// 这样生成的路径是 D:\JavaProject\study-java\src\main\java
gc.setOutputDir(projectPath + "/src/main/java");
// XML ResultMap
gc.setBaseResultMap(true);
// XML column List
gc.setBaseColumnList(true);
//使用swagger2 生成 api 文档
gc.setSwagger2(true);
//设置主键生成策略
gc.setIdType(IdType.AUTO);
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setUrl("jdbc:mysql://localhost:3306/studyPlus?useUnicode=true&characterEncoding=utf8&useSSL=true&useServerPrepStmts=true");
mpg.setDataSource(dsc);

// 策略配置
StrategyConfig strategy = new StrategyConfig();
// strategy.setCapitalMode(true);// 全局大写命名 ORACLE 注意
//实体类使用lombok
strategy.setEntityLombokModel(true);
//rest风格controller
strategy.setRestControllerStyle(true);
//is_xxx 去除is_ 前缀
strategy.setEntityBooleanColumnRemoveIsPrefix(true);
//字段注释
strategy.setEntityTableFieldAnnotationEnable(true);
//数据表命名策略
strategy.setNaming(NamingStrategy.underline_to_camel);
//数据表列生成策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 需要生成的表
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
mpg.setStrategy(strategy);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块的名称"));
pc.setParent("com.alsritter.ant");
mpg.setPackageInfo(pc);
// 执行生成
mpg.execute();
}
}